package guet.imagan.progbuilder;

import guet.imagan.util.FileUtil;
import guet.imagan.util.ListUtil;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * 表示编译器,它是抽象类
 */
public abstract class Compiler {
	/**
	 * 创建编译器进程的构建器对象
	 */
	private final ProcessBuilder processBuilder;
	/**
	 * 编译器编译生成的目标文件的扩展名（小写），例如 exe
	 */
	private String targeFileExt;
	/**
	 * 编译器能够编译的源文件扩展名列表，如 c.cpp
	 */
	private final List<String> srcFileExts;
	/**
	 * 编译器的显示名称
	 */
	private String compilerName;
	/**
	 * 编译命令行
	 */
	private final List<String> cmdLine;
	/**
	 * 编译超时时间，单位：秒
	 */
	private long timeout;
	/**
	 * 编译器路径
	 */
	private String compilerPath="";

	/**
	 * 返回编译器所支持的源文件列表
	 *
	 * @return
	 */
	public List<String> getSrcFileExts() {
		return srcFileExts;
	}



	/**
	 * 创建一个没有初始化命令行参数的编译器对象
	 */
	public Compiler() {
		processBuilder = new ProcessBuilder();
		srcFileExts = new ArrayList<>();
		compilerName = "compiler";
		cmdLine = new ArrayList<>();
		timeout = 1;

	}

	/**
	 * 设置编译器能够编译的源文件扩展名的范围
	 *
	 * @param exts 源文件扩展名 该扩展名列表元素被复制到编译器的srcFileExts中
	 */
	public void setSrcFileExt(List<String> exts) {
		srcFileExts.clear();
		srcFileExts.addAll(exts);
	}

	/**
	 * 设置编译器编程生成的目标文件的扩展名
	 *
	 * @param ext 目标文件的扩展名
	 */
	public void setTargeFileExt(String ext) {
		this.targeFileExt = ext;
	}


	/**
	 * 对一个项目进行编译构建,该构建并不检测项目源文件扩展名与编译器所能编译的源文件的扩展名是否匹配
	 *
	 * @param project 待构建的项目
	 * @return 构建后生成的启动程序的文件，对于纯编译型语言来说，返回的是目标程序文件
	 * @throws CompilerException
	 */
	public List<String> compile(Project project) throws Project.ProjectException, CompilerException {
		File outputDir=project.getOutputDir();
		processBuilder.directory(outputDir);
		TextFile errFile = createErrTextFile(outputDir);
		processBuilder.redirectError(errFile);
		List<String> cmd = resetCmdLine();
		populatePlaceHolder(cmd,project);
		Process p = null;
		try {
			p = processBuilder.start();
		} catch (IOException e) {
			throw new CompilerException(e.getMessage());
		}
		try {
			p.waitFor(timeout, TimeUnit.SECONDS);
		} catch (InterruptedException e) {
			throw new CompilerException(e.getMessage());
		}
		List<String> errLine = errFile.getContent();
		if (errLine != null) {
			StringBuilder sb = new StringBuilder();
			errLine.forEach(line -> sb.append(line).append("\n"));
			throw new CompilerException(sb.toString());
		}
		return getCompileResultString(outputDir);

	}

	/**
	 * 根据当前Project对象，填充编译器命令行中的占位符
	 * @param cmd 命令行对象
	 * @param project 当前要编译的项目
	 */
	public abstract void populatePlaceHolder(List<String> cmd,Project project);

	private List<String> resetCmdLine() {
		List<String> cmd = processBuilder.command();
		cmd.clear();
		cmd.addAll(cmdLine);
		return cmd;
	}

	/**
	 * 获得目标目录下所有编译生成的目标文件的路径
	 *
	 * @param targetPath 目标目录
	 * @return 编译生成的目标文件列表
	 */
	private List<String> getCompileResultString(File targetPath) throws Project.ProjectException {
		List<File> result = new ArrayList<>();
		FileUtil.findFiles(result,targetPath, Arrays.asList(targeFileExt));
		return FileUtil.getPathsOfFiles(result);
	}

	/**
	 * 在指定的目录下创建错误文件对象（该文件并没有被实际创建）
	 *
	 * @param targetPath 目标目录
	 * @return 错误文件（其文件名为error.txt)
	 */
	public static TextFile createErrTextFile(File targetPath) {
		String errFilePath = targetPath.getAbsolutePath() + File.separator
				+ "error.txt";
		TextFile errFile = new TextFile(errFilePath);
		return errFile;
	}

	/**
	 * 设置编译器编译命令行
	 *
	 * @param cmd 编译命令行，该命令行是复制而不是同步。
	 */
	public void setCommand(List<String> cmd) {
		cmdLine.clear();
		cmdLine.addAll(cmd);
	}

	/**
	 * 返回编译器的显示名称
	 *
	 * @return
	 */
	public String getCompilerName() {
		return compilerName;
	}

	/**
	 * 设置编译器的显示名称
	 *
	 * @param compilerName
	 */
	public void setCompilerName(String compilerName) {
		this.compilerName = compilerName;
	}

	/**
	 * 返回编译生成的目标文件的扩展名
	 *
	 * @return
	 */
	public String getTargeFileExt() {
		return targeFileExt;
	}

	/**
	 * 返回编译器编译超时时间
	 *
	 * @return
	 */
	public long getTimeout() {
		return timeout;
	}

	/**
	 * 设置编译器超时时间
	 *
	 * @param timeout
	 */
	public void setTimeout(long timeout) {
		this.timeout = timeout;
	}

	/**
	 * 返回编译器路径
	 * @return 编译器路径
	 */
	public String getCompilerPath() {
		if(compilerPath.equals("")){
			if(cmdLine.size()>0){
				return cmdLine.get(0);
			}
			return "";
		}
		return compilerPath;
	}

	/**
	 * 设置编译器路径
	 * @param compilerPath 编译器路径
	 */
	public void setCompilerPath(String compilerPath) {
		cmdLine.remove(0);
		cmdLine.add(0, compilerPath);
		this.compilerPath=compilerPath;
	}
}
